/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved
 * (c) 2001 - 2013 OpenPlans
 * This code is licensed under the GPL 2.0 license, available at the root
 * application directory.
 */
package org.geoserver.wps.web;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.extensions.ajax.markup.html.autocomplete.AutoCompleteBehavior;
import org.apache.wicket.extensions.ajax.markup.html.autocomplete.AutoCompleteSettings;
import org.apache.wicket.extensions.ajax.markup.html.autocomplete.IAutoCompleteRenderer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.CheckBox;
import org.apache.wicket.markup.html.form.ChoiceRenderer;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.RadioChoice;
import org.apache.wicket.markup.html.form.SubmitLink;
import org.apache.wicket.markup.html.form.TextArea;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.model.StringResourceModel;
import org.apache.wicket.validation.validator.RangeValidator;
import org.geoserver.security.CatalogMode;
import org.geoserver.security.GeoServerRoleService;
import org.geoserver.security.impl.GeoServerRole;
import org.geoserver.security.web.AbstractSecurityPage;
import org.geoserver.web.wicket.GeoServerDataProvider.Property;
import org.geoserver.web.wicket.GeoServerTablePanel;
import org.geoserver.web.wicket.ParamResourceModel;
import org.geoserver.wps.ProcessGroupInfo;
import org.geoserver.wps.WPSInfo;

/**
 * A page listing all WPS groups, allowing enable/disable single groups and add/remove roles to
 * grant access to all its processes This page links to WPS service security configuration page to
 * works on single processes.
 *
 * @see ProcessSelectionPage
 */
@SuppressWarnings("serial")
public class WPSAccessRulePage extends AbstractSecurityPage {

    static final List<CatalogMode> CATALOG_MODES =
            Arrays.asList(CatalogMode.HIDE, CatalogMode.MIXED, CatalogMode.CHALLENGE);

    private List<ProcessGroupInfo> processFactories;
    private WPSInfo wpsInfo;
    private RadioChoice catalogModeChoice;

    private List<String> availableRoles = new ArrayList<String>();

    public WPSAccessRulePage() {
        wpsInfo = getGeoServer().getService(WPSInfo.class);
        Form form = new Form("form", new CompoundPropertyModel(wpsInfo));

        processFactories = cloneFactoryInfos(wpsInfo.getProcessGroups());
        ProcessFactoryInfoProvider provider =
                new ProcessFactoryInfoProvider(processFactories, getLocale());
        GeoServerRoleService roleService = getSecurityManager().getActiveRoleService();
        try {
            for (GeoServerRole r : roleService.getRoles()) {
                availableRoles.add(r.getAuthority());
            }
        } catch (IOException e1) {
            LOGGER.log(Level.FINER, e1.getMessage(), e1);
        }

        TextField<Integer> maxComplexInputSize =
                new TextField<Integer>("maxComplexInputSize", Integer.class);
        maxComplexInputSize.add(RangeValidator.minimum(0));
        form.add(maxComplexInputSize);

        final AutoCompleteSettings settings = new AutoCompleteSettings();
        settings.setShowCompleteListOnFocusGain(false);
        settings.setShowListOnEmptyInput(true);
        settings.setShowListOnFocusGain(true);
        settings.setMaxHeightInPx(100);

        GeoServerTablePanel<ProcessGroupInfo> processFilterEditor =
                new GeoServerTablePanel<ProcessGroupInfo>("processFilterTable", provider) {

                    @Override
                    protected Component getComponentForProperty(
                            String id,
                            final IModel<ProcessGroupInfo> itemModel,
                            Property<ProcessGroupInfo> property) {

                        if (property.getName().equals("enabled")) {
                            Fragment fragment =
                                    new Fragment(id, "enabledFragment", WPSAccessRulePage.this);
                            CheckBox enabled =
                                    new CheckBox(
                                            "enabled",
                                            (IModel<Boolean>) property.getModel(itemModel));
                            enabled.setOutputMarkupId(true);
                            fragment.add(enabled);
                            return fragment;
                        } else if (property.getName().equals("prefix")) {
                            return new Label(id, property.getModel(itemModel));
                        } else if (property.getName().equals("title")) {
                            return new Label(id, property.getModel(itemModel));
                        } else if (property.getName().equals("summary")) {
                            return new Label(id, property.getModel(itemModel));
                        } else if (property.getName().equals("roles")) {
                            Fragment fragment =
                                    new Fragment(id, "rolesFragment", WPSAccessRulePage.this);
                            TextArea<String> roles =
                                    new TextArea<String>(
                                            "roles",
                                            (IModel<String>) property.getModel(itemModel)) {
                                        public <C extends Object>
                                                org.apache.wicket.util.convert.IConverter<C>
                                                        getConverter(java.lang.Class<C> type) {
                                            return new RolesConverter(availableRoles);
                                        };
                                    };
                            StringBuilder selectedRoles = new StringBuilder();
                            IAutoCompleteRenderer<String> roleRenderer =
                                    new RolesRenderer(selectedRoles);
                            AutoCompleteBehavior<String> b =
                                    new RolesAutoCompleteBehavior(
                                            roleRenderer, settings, selectedRoles, availableRoles);
                            roles.setOutputMarkupId(true);
                            roles.add(b);
                            fragment.add(roles);
                            return fragment;
                        } else if (property.getName().equals("edit")) {
                            Fragment fragment =
                                    new Fragment(id, "linkFragment", WPSAccessRulePage.this);
                            // we use a submit link to avoid losing the other edits in the form
                            Link link =
                                    new Link("link") {
                                        @Override
                                        public void onClick() {
                                            ProcessGroupInfo pfi =
                                                    (ProcessGroupInfo) itemModel.getObject();
                                            setResponsePage(
                                                    new ProcessSelectionPage(
                                                            WPSAccessRulePage.this, pfi));
                                        }
                                    };
                            fragment.add(link);

                            return fragment;
                        }

                        return null;
                    }
                };
        processFilterEditor.setFilterable(false);
        processFilterEditor.setPageable(false);
        processFilterEditor.setOutputMarkupId(true);
        form.add(processFilterEditor);

        form.add(
                new AjaxLink("processAccessModeHelp") {
                    @Override
                    public void onClick(AjaxRequestTarget target) {
                        dialog.showInfo(
                                target,
                                new StringResourceModel(
                                        "processAccessModeHelp.title", getPage(), null),
                                new StringResourceModel(
                                        "processAccessModeHelp.message", getPage(), null));
                    }
                });
        catalogModeChoice =
                new RadioChoice(
                        "processAccessMode",
                        new PropertyModel<CatalogMode>(wpsInfo, "catalogMode"),
                        CATALOG_MODES,
                        new CatalogModeRenderer());
        catalogModeChoice.setSuffix(" ");
        form.add(catalogModeChoice);

        SubmitLink submit =
                new SubmitLink("submit", new StringResourceModel("save", (Component) null, null)) {
                    @Override
                    public void onSubmit() {
                        try {
                            // overwrite the process factories that we did clone to achieve
                            // isolation
                            List<ProcessGroupInfo> factories = wpsInfo.getProcessGroups();
                            factories.clear();
                            factories.addAll(processFactories);
                            getGeoServer().save(wpsInfo);
                            doReturn();
                        } catch (Exception e) {
                            error(e);
                        }
                    }
                };
        form.add(submit);

        Button cancel =
                new Button("cancel") {
                    public void onSubmit() {
                        doReturn();
                    }
                };
        form.add(cancel);

        add(form);
    }

    private List<ProcessGroupInfo> cloneFactoryInfos(List<ProcessGroupInfo> processFactories) {
        List<ProcessGroupInfo> result = new ArrayList<ProcessGroupInfo>();
        for (ProcessGroupInfo pfi : processFactories) {
            result.add(pfi.clone());
        }

        return result;
    }

    class CatalogModeRenderer extends ChoiceRenderer {

        public Object getDisplayValue(Object object) {
            return new ParamResourceModel(((CatalogMode) object).name(), getPage()).getObject();
        }

        public String getIdValue(Object object, int index) {
            return ((CatalogMode) object).name();
        }
    }
}
